プログラミングやRPG(作るほう)が好きな人の日記
このホームページは毎日 夜11時にアクセスできなくなります。朝6時半に再開されます。
(世の中のネット依存対策として)
そんなひとことを30代くらいの若手プログラマーの人から聞いたことがあります。
N88-BASIC という言語を用いて、「オブジェクト指向のカプセル化の有無の違い」について見ていきましょう。
N88-BASICを用いるのには理由があります。
オブジェクト指向の良さが、どうもわからないという方は、BASICの時代のプログラミングを知らないからオブジェクト指向の良さがわからないんだと思います。N88-BASICで、オブジェクトどうしの区分けがなく、メソッドもない、そんな不便な世界を知っていれば、オブジェクト指向の良さがわかると思うんです。
……ただ、今回はアプローチがちょっと違っていて、なぜか、N88-BASICでオブジェクト指向と同じことをやろうとしています…
まぁ、見ていきましょうか。左のタブから順に見ていってください。
20行目がアクセスしたいデータです。
40行目以降、データにアクセスしています。
10 'カプセル化実験 カプセル化していない例
20 NAM$="アムロ・アズナブル" 'データの持ち方
30 '以降、データの利用
40 PRINT NAM$ + "は出撃した。"
50 PRINT NAM$ + "は言った。「シャア・レイの気配がする」"
60 PRINT NAM$ + "は言った。「やっぱりか」"
70 PRINT NAM$ + "は言った。「しゃあ・ねぇな!やったるか!」"
80 '
90 '
結果はちゃんと名前が展開されました。
20行目がアクセスしたいデータです。仕様変更か何かでデータの持ち方が変更されています。
40行目以降、データにアクセスしています。
10 'カプセル化実験 カプセル化していない例
20 FIRSTNAME$ = "アムロ" : SECONDNAME$ = "アズナブル" 'データの持ち方を変更
30 '以降、データの利用
40 PRINT NAM$ + "は出撃した。"
50 PRINT NAM$ + "は言った。「シャア・レイの気配がする」"
60 PRINT NAM$ + "は言った。「やっぱりか」"
70 PRINT NAM$ + "は言った。「しゃあ・ねぇな!やったるか!」"
80 'データを利用している部分をすべて変更しなければならない。
90 '
データの持ち方の変更に合わせて、40行目以降もすべてアクセスの仕方を変更しないと、名前は展開されません。
20行目がアクセスしたいデータです。
25行目ではアクセサメソッド(メソッドじゃないけど)が定義されています。
40行目以降、データにアクセサメソッドでアクセスしています。
10 'カプセル化実験 カプセル化している例
20 NAM$="アムロ・アズナブル" 'データの持ち方
25 DEFFNA = NAM$ 'データの返し方
30 '以降、データの利用
40 PRINT (FNA) + "は出撃した。"
50 PRINT (FNA) + "は言った。「シャア・レイの気配がする」"
60 PRINT (FNA) + "は言った。「やっぱりか」"
70 PRINT (FNA) + "は言った。「しゃあ・ねぇな!やったるか!」"
80 '
結果は名前が展開されました。
20行目がアクセスしたいデータです。仕様変更か何かでデータの持ち方が変更されています。
25行目ではアクセサメソッドが定義されています。ここも返し方を変更しています。
40行目以降、データにアクセサメソッドでアクセスしています。ここはすべて変更不要です。
10 'カプセル化実験 カプセル化している例
20 FIRSTNAME$ = "アムロ" : SECONDNAME$ = "アズナブル" 'データの持ち方を変更
25 DEFFNA = FIRSTNAME$ + "・" + SECONDNAME$ 'データの返し方も変更
30 '以降、データの利用
40 PRINT (FNA) + "は出撃した。"
50 PRINT (FNA) + "は言った。「シャア・レイの気配がする」"
60 PRINT (FNA) + "は言った。「やっぱりか」"
70 PRINT (FNA) + "は言った。「しゃあ・ねぇな!やったるか!」"
80 'データを利用している部分はいっさい変更しなくてもよい。
この多くのデータアクセス部分の変更不要が、カプセル化のメリットです。
このタブインターフェースは私が作成したものです。(紹介ページへ)
カプセル化はデータを提供する側とデータを利用する側との間にアクセサメソッドを用意することで、提供する側のデータの持ち方の変更の影響を 利用する側に見せないようにしている…ということなんだと思います。
カプセル化のためには、クラスという概念は必ずしも必要ではなく、提供する側と利用する側との間に何か1つ緩衝材(かんしょうざい)のようなしくみがあればいいんだと思います。(ただ世間ではクラスを使うことを前提としていることが多いので、必要と考えておいたほうが良いです)
そうすることで変更に強いプログラムになるわけですが、のちのちの修正の手間を見越して、あらかじめ回避しようという作業なので、ちょっと退屈に感じるかもしれません。その良さを実感するのは今ではなく、将来 仕様変更や修正をするときですから。保険とか国民年金とか払っても面白くないのと似ています。
2024年1月6日追記:
Borland Turbo C ++ 1.02 マニュアル「INTRODUCTION」P151 (1990年発行)
(第5章 C++ 入門)
”カプセル化とは、構造化されたデータとそのデータを扱うための関数(アクションまたはメソッドと呼ばれます)を組み合わせて一体化させることです。”
Wikipedia - カプセル化
”カプセル化は、コンピュータプログラミングで用いられる概念で互いに関連するデータとロジックなどを1つのモジュールとしてまとめることである。また、より広い意味ではまとめたモジュールの内側の詳細を外側から隠蔽することをも含む。”
… 隠ぺいされることで、内側の実装方法に関わらず、外側はアクセスすることができます。言い換えれば、内部データに直接アクセスされることがないので安心して実装方法の変更ができます。
このように隠ぺいした際の、アクセサメソッドの はたらき に注目して私は上記のような記事を書いていました。
このアクセサメソッドの はたらき だけでカプセル化を説明しようとしたのは、わかりやすいかもしれませんが、ちょっと短絡的だったかなと思います。
「会社から言われてやってる」じゃ、つらいんじゃないかな?
(なお、私はオブジェクト指向は数年前に本を読んで勉強してましたが、カプセル化等 ほとんど忘れている状態であって、今日思い立ってこの記事を書くにあたって当時の本を引っ張り出した、といった感じです)
勉強の仕方ですが、前から見る方法だけでも勉強できますが、後ろから見る方法もおススメです。
オブジェクト指向の良さは、
の3本立て、ということですが(今本見ています)
今回話題にしたカプセル化は 変更に強い ことに効果があるのであって、ほかの2つの 再利用 と、拡張性 については(書籍などをよく見てみると)特に言及されていない、という事実があります。(何かの本ではほかの2つにも効果があると言っているかもしれませんが、私の持っている本では特にそうは言っていないようです)
前から見るというのは「変更に強い、以上」という勉強の仕方であって、
後ろから見るというのは「変更に強い、また、再利用については無関係、拡張性についても無関係」とすみわけをはっきりさせるということです。
「カプセル化は再利用や拡張性に効果がある?」と聞かれて、「ないんじゃないか?」とはっきり答えられると良い、ということです。
N88-BASICを久しぶりに使ったんですが、いろいろ発見がありました。
このホームページの機能追加を図ったんですが、結局ダメでボツになってしまった。丸一日かけたのに…なんだったんだ…orz
(具体的に言うと、このホームページ上でExcelVBAと書いたら、自動的に「このホームページ上で ExcelVBA と書いたら」とアルファベットの単語の前後に半角スペースが入り読みやすくなる、という機能を作っていました。でもこのホームページ上のいろいろなリンクやスクリプトが動かなくなってしまったので、あきらめました)
話は変わって、色違いのSVC画像を作りました。
Excelで作成しているSVCエディタは、色の一括変更とか、持ち物の切り替えとか、影色作成とかいろいろできるようになってきました。
遊びで翼を付けてみました。
この翼を付ける作業が、ちょいちょいちょいでできてしまって…ずいぶんと簡単なんです。
たたむとこんな感じです。
ガンダムみたいですね。
コンピューターゲームの音楽の作曲家である古代祐三氏によって1987年に開発された「MUCOM88」という音楽製作ツールが無償公開されました。
PC-8801シリーズで動作するものと、Windowsで動作するものの2種類が公開されています。
そこで、私も Windows 版のほうをダウンロードして試してみました。
(リズム音源を別途ダウンロードしてMUCOM88 Windowsと同じフォルダに配置するなどサイトの説明のとおりに行います)
このサイトの説明のとおりに A t190@30v15 cdefgab>c というMML(音楽演奏言語)文字列を黒い画面に挿入して、画面の「PLAY」ボタンを押すと、すぐに音が鳴りました。Windowsだけど、ちゃんとYM2203/YM2608のFM音源(のエミュレート?)で鳴っているようです。
画面の「MENU」ボタンを押すとMMLファイルの管理画面(ホーム)になります。
提供サイトではPC-8801版とWindows版の両方でサンプルが用意されていますが、両方とも同じ内容のようです。
サンプルは過去に古代祐三氏が作曲し他社ゲームソフト内で使われた楽曲で、同氏に著作権があるそうです。
(※PC-8801版はサンプル1つ1つがディスクイメージファイルになっていたので、Windows版のサンプルをダウンロードしたほうが良いと思います)
サンプルのフォルダに移動(マウスではなくキーボードのEnterキーで移動)して、.muc形式のファイルを選んで、「再生」ボタンを押すと、iTunes storeでダウンロードした「アクトレイザー フィルモア」と同じ曲が流れました。あの繊細な音源がMMLのレベルで手元にあるなんて、なんてありがたいことだろう。
サンプルはもちろん古代祐三氏の著作権がありますが、このツールでユーザーが作成した音楽は録音して自分のゲームに使うなりいろいろできると思うので、ほかのシーケンサーソフトを使わずにこちらを使わせていただく…なんてことができると思います。
あと、同氏は作曲家であると同時にプログラマーでもあるとは初めて知りました。FM音源のドライバソフトも自作していたということで、YM2203のICチップのデータシートみたいなものも読んでいた可能性もあるのでひょっとしたら電気関係もある程度ご存知かもしれない…。
先月の冒頭プログラムのグラディウスのマネですが、「まだ遊べないです☆彡」という状態だったのを少し遊べるようにしました。
「まだ遊べないです」と書いてそのままっていうのも残念な気がしたから。
下の画像をクリックすると11月ページの冒頭へ移動し、ゲームが開始されます…が、コンピューターの性能を高く要求するかもしれません。
横長だとダライアス(画像検索)みたいだ。
フルスクリーン版もあります。これ (F11キーを押してブラウザをフルスクリーンにすると楽しいかも。戻すには再度F11です)
またExcel画像の使いまくり。
音楽がなくて寂しいし、自分は死なないし、ステージは途中で終わってるしで、途中だけど。
グラディウスみたいなの作っていて、グラディウスのひとつのステージを作るのって まるで一枚のCGを描いているようだなって思いました。
1つ1つの敵キャラのふるまいを調整して、プレイヤーを楽しませる。ステージ全体を とおして調整していき、1つのステージを完成させる。
CGもそんな感じで描いている気がします。
参考にMicrosoft Storeの無料の「PCエンジン版グラディウス」をダウンロードして、見てみたんですが、すんげーゴージャスじゃないか。自分のと比べて。
やっぱりプロの仕事は違うな。ほか作品との競争であったり、プレイヤーが払ったお金に見合うできばえを求めたり、過去の経験を駆使したりといろいろな要素でゴージャスになっているんだな、と思いました。
このプログラムのリスト(リスト中のコメントはあまり信用しないでください)
//- 関数リスト - ..
// 1: app.init ..
// 2: app.onpreloadx ..
// 3: app.afterOnload ..
// 4: app.scheduleAdd ..
// 5: app.newEnemy ..
// 6: app.entrySprite ..
// 7: app.deleteSprite ..
// 8: app.run ..
// 9: app.addStar ..
// 10: app.drawTextCenter ..
// 11: app.draw ..
// 12: app.keyType ..
// 13: app.keySense ..
// 14: app.fire ..
// 15: ImagePalette ..
// 16: ImagePalette.prototype.drawImage ..
// 17: ImagePalette.prototype.drawImage_single ..
// 18: Sprite ..
// 19: Sprite.prototype.setWidth ..
// 20: Sprite.prototype.setHeight ..
// 21: Sprite.prototype.setWidthWithAspectHeight ..
// 22: Sprite.prototype.draw ..
// 23: Sprite.prototype.frame ..
// 24: Sprite.prototype.moveFunction ..
// 25: Sprite.prototype.animFrameChecker ..
// 26: Sprite.prototype.moveFrameChecker ..
// 27: Maps ..
// 28: Maps.prototype.ready ..
// 29: Maps.prototype.frame ..
// 30: Maps.prototype.draw ..
// 31: Map ..
console.log( "20181125-index.js loading.." );
//---今月のスクリプト
/*
this.canvasApply(200,200); は何をしているのか?
this.canvasEL.style.width を変更している
this.canvasEL.style.height を変更している
this.pixelsize を変更している
this.resetMozaic(); を実行している
以上
this.ready(); は何をしているか?
this.draw() を実行
this.onscrollx() を意図的実行
onscrollx()はcanvasが視聴者の目に入ったかどうかを見ている。
目に入ったら、this.start()、入らなくなったらthis.stop()を実行している。
scroll のイベントリスナを設定
以上
コンストラクタAppはcanvasをどのように用意しているか?
function App( id, element ) {
引数のelementについてタグ名がCANVASなら、elementをthis.canvasELとする。
canvasではないなら、canvas自動生成を行う。
canvas自動生成は、elementを親としてcanvasを設置する。
(そのIDはid + "_canvas"である)
プログラムの流れ
app.init()
一時終了
画像がロードされて、app.onpreloadx()
すべてロードされて、app.afterOnload()
app.ready()→app.onscrollx()→this.start()
タイマで、app.run()
キー入力で、app.keyType()、app.keySense()
マウスで、app.onmousedownx()、app.onclickx()
タッチで、app.ontouchx()未開発
対応付け
一般 このプログラム
init afterOnload
*/
var parentEL = document.getElementById( "sl_monthlyJS_canvas" );
var app = new App( "sl_monthlyJS" );
addEventListener( "load", app.exec.bind( app, parentEL ), false );
//関数 1 / 31 ..
app.init = function() {
console.log( "-init" );
//canvasについて設定
this.canvasEL.style.border = "solid 0px red";
if( typeof baseURL !== "undefined" ) {
this.baseURL = baseURL;
this.setCanvasSize( setCanvasSizeW, setCanvasSizeH );
this.pixelsize = pixelsize; //モザイクサイズ
this.isWidth100per = isWidth100per; //canvasを親要素の横幅に合わせる
this.isScreenFit = isScreenFit;
this.isKeepAspect = isKeepAspect; //そのとき縦横アスペクト比を維持する
this.isPixelZoom = isPixelZoom; //ドットストレッチ
this.isSmoothZoom = isSmoothZoom; //スムーズストレッチ
} else {
this.baseURL = "20181125-indexJS/";
this.setCanvasSize( 512, 256 );
this.pixelsize = 2; //モザイクサイズ
this.isWidth100per = 1; //canvasを親要素の横幅に合わせる
this.isScreenFit = 0;
this.isKeepAspect = 0; //そのとき縦横アスペクト比を維持する
this.isPixelZoom = 0; //ドットストレッチ
this.isSmoothZoom = 0; //スムーズストレッチ
}
this.canvasEL.style.backgroundColor = "black";
//設定後の適用
this.canvasApply();
var p = document.getElementById( "whiteareaID" );
this.toBackgroundOf( p );
//定数
this.EnemyType = 1;
this.SupporterMissileType = 2;
this.FighterType = 4;
this.a = 0;
this.toA = -1;
this.toAMode = false;
this.pause = false;
//---画像
this.images = new Object();
this.imageSRCs = [
"map1.png",
"map2.png",
"map3.png",
"message1.png",
"fighter.png",
"sm.png",
"enemy1.png",
"enemy2.png",
"enemy3.png",
];
this.drawFLG = "preload";
this.preloadCNT = 0;
this.draw();
for( var i = 0; i < this.imageSRCs.length; i++ ) {
var img = new Image();
img.onload = this.onpreloadx.bind( this );
img.src = this.baseURL + this.imageSRCs[ i ];
img.name = this.imageSRCs[ i ].match( /^(.+)\./ )[ 1 ];
this.images[ img.name ] = img;
}
};
//関数 2 / 31 ..
app.onpreloadx = function( e ) {
this.preloadCNT++;
console.log( "preloaded: " + this.preloadCNT + "/" + Object.keys( this.images ).length + " : " + e.target.name );
this.draw();
//check.
if( 1 && this.preloadCNT == Object.keys( this.images ).length ) this.afterOnload();
};
//関数 3 / 31 ..
app.afterOnload = function() {
console.log( "afterOnload" );
this.drawFLG = "";
//星々
this.stars = new Array();
for( var i = 0; i < 30; i++ ) {
this.addStar( Math.random() * this.canvasW );
}
this.supporterMissiles = new Array();
this.enemies = new Array();
this.sprites = new Object();
this.enemyCnt = 0;
this.supporterMissileCnt = 0;
//自機
this.fighter = new Sprite( "fighter" );
with( this.fighter ) {
imagePalette = new ImagePalette( this.cc, this.images.fighter, 142, 36, 90, 36 );
x = 105;
y = 80;
setWidthWithAspectHeight( 38 );
type = this.FighterType;
}
this.entrySprite( this.fighter );
this.maps = new Maps( this );
//---スケジュール
this.schedule = new Array();
this.scheduleAdd( 5, function() {
var enemy = this.newEnemy( 2 );
enemy.y = this.canvasH / 3;
} );
var nico = function( tm ) {
//ニコ編隊
for( var j = 0; j < 5; j++ ) {
for( var i = 0; i < 5; i++ ) {
var tm2 = tm + i*2 + j * 50;
if( j % 2 ) {
this.scheduleAdd( tm2, function() {
var enemy = this.newEnemy( 1 );
enemy.y = this.canvasH / 3;
} );
} else {
this.scheduleAdd( tm2, function() {
var enemy = this.newEnemy( 1 );
enemy.y = this.canvasH / 3 * 2;
} );
}
}
}
}.bind( this );
nico( 10 );
nico( 650 );
//くるくるりぼん
tm = 350;
this.scheduleAdd( tm, function() { ( this.newEnemy( 2 ) ).y = this.canvasH / 4; } );
this.scheduleAdd( tm, function() { ( this.newEnemy( 2 ) ).y = this.canvasH / 4 * 3; } );
//上下サンドイッチ隊
var sandwich = function( tm ) {
this.scheduleAdd( tm, function() { this.newEnemy( 3, 1 ); } );
this.scheduleAdd( tm, function() { this.newEnemy( 3, 2 ); } );
this.scheduleAdd( tm, function() { this.newEnemy( 3, 3 ); } );
this.scheduleAdd( tm, function() { this.newEnemy( 3, 4 ); } );
this.scheduleAdd( tm, function() { this.newEnemy( 3, 5 ); } );
this.scheduleAdd( tm, function() { this.newEnemy( 4, 1 ); } );
this.scheduleAdd( tm, function() { this.newEnemy( 4, 2 ); } );
this.scheduleAdd( tm, function() { this.newEnemy( 4, 3 ); } );
this.scheduleAdd( tm, function() { this.newEnemy( 4, 4 ); } );
this.scheduleAdd( tm, function() { this.newEnemy( 4, 5 ); } );
}.bind( this );
sandwich( 540 );
sandwich( 950 );
this.scheduleAdd( 300, function() {
this.maps.ready( "map" );
} );
this.scheduleAdd( 1200, function() {
this.maps.ready( "message" );
} );
this.timerMS = 50;
this.ready();
};
//関数 4 / 31 ..
app.scheduleAdd = function( tm, func ) {
//check.
if( ! this.schedule[ tm ] ) this.schedule[ tm ] = new Array();
this.schedule[ tm ].push( func.bind( this ) );
}
//関数 5 / 31 ..
app.newEnemy = function( type, mutation ) {
var id = "enemy" + this.enemyCnt++;
var enemy = new Sprite( id, this );
with( enemy ) {
type = this.EnemyType;
imagePalette = new ImagePalette( this.cc, this.images.enemy1, 72, 50, 51, 50 );
x = this.canvasW;
y = this.canvasH / 2;
frame = function() {
//this is sprite.
this.moveFrameChecker( this.x < 0 );
this.animFrameChecker();
};
}
switch( type ) {
case 1:
//ニコ
with( enemy ) {
frame = function() {
//this is sprite.
this.moveFrameChecker( this.x < 0 );
this.imagePalette.index ++;
//check.
if( this.imagePalette.index > this.imagePalette.maxIndex ) this.imagePalette.index = 0;
};
moveFunction = function() {
var bx = this.x;
this.x -= this.speed / 2;
var f = function( x ) { return Math.sin( x * 0.1 ) * 30; }
this.y += f( this.x ) - f( bx );
};
}
break;
case 2:
//くるくるりぼん
with( enemy ) {
width = 48;
height = 48;
imagePalette = new ImagePalette( this.cc, this.images.enemy2, 72, 75, 60, 75 );
speed = 8;
animFrameMax = 3;
}
break;
case 3:
//サンドイッチ隊上から
with( enemy ) {
x = app.canvasW / 2 + 50 * mutation;
y = 0;
imagePalette = new ImagePalette( this.cc, this.images.enemy3, 72, 60, 60, 60 );
imagePalette.index = mutation;
width = 48;
height = 48;
animFrameMax = 4;
frame = function() {
//this is sprite.
this.moveFrameChecker( this.y > app.canvasH );
this.animFrameChecker();
};
moveFunction = function() {
this.x -= this.speed / 2;
this.y += this.speed;
};
}
break;
case 4:
//サンドイッチ隊下から
with( enemy ) {
x = app.canvasW / 2 + 50 * mutation;
y = app.canvasH;
imagePalette = new ImagePalette( this.cc, this.images.enemy3, 72, 60, 60, 60 );
imagePalette.index = mutation;
width = 48;
height = 48;
animFrameMax = 4;
frame = function() {
//this is sprite.
this.moveFrameChecker( this.y < 0 );
this.animFrameChecker();
};
moveFunction = function() {
this.x -= this.speed / 2;
this.y -= this.speed;
};
}
break;
}
this.entrySprite( enemy );
return enemy;
};
//関数 6 / 31 ..
app.entrySprite = function( sprite ) {
switch( sprite.type ) {
case this.EnemyType:
this.enemies.push( sprite );
break;
case this.SupporterMissileType:
this.supporterMissiles.push( sprite );
break;
case this.FighterType:
break;
default:
alert( "error at app.entrySprite\n\nundefined type: " + sprite.type );
}
this.sprites[ sprite.id ] = sprite;
}
//関数 7 / 31 ..
app.deleteSprite = function( sprite ) {
switch( sprite.type ) {
case this.EnemyType:
this.enemies.splice( this.enemies.indexOf( sprite ), 1 );
break;
case this.SupporterMissileType:
this.supporterMissiles.splice( this.supporterMissiles.indexOf( sprite ), 1 );
break;
case this.FighterType:
break;
default:
alert( "error at app.deleteSprite\n\nundefined type: " + sprite.type );
}
delete this.sprites[ sprite.id ];
};
//frame
//関数 8 / 31 ..
app.run = function() {
this.keySense();
//debug.
if( this.toAMode ) {
if( this.a == this.toA ) {
this.stop();
this.toAMode = false;
this.timerMS = this.toABak;
this.pause = true;
this.start();
}
}
if( this.pause ) {
this.draw();
return;
}
//スプライト
for( var name in this.sprites ) {
var sprite = this.sprites[ name ];
//check.
if( sprite.deleteFlg ) {
this.deleteSprite( sprite );
continue;
}
sprite.frame();
}
//スケジュール
if( this.schedule[ this.a ] ) {
for( var i = 0; i < this.schedule[ this.a ].length; i++ ) {
this.schedule[ this.a ][ i ]();
}
}
//地上
this.maps.frame();
//当たり判定
for( var i = 0; i < this.supporterMissiles.length; i++ ) {
var sm = this.supporterMissiles[ i ];
for( var j = 0; j < this.enemies.length; j++ ) {
var en = this.enemies[ j ];
//check.
if( en.deleteFlg ) continue;
var hitX1 = sm.x >= ( en.x - en.centerX );
var hitX2 = sm.x <= ( en.x + en.centerX );
var hitY1 = sm.y >= ( en.y - en.centerY );
var hitY2 = sm.y <= ( en.y + en.centerY );
if( hitX1 && hitX2 && hitY1 && hitY2 ) {
sm.deleteFlg = true;
en.deleteFlg = true;
break;
}
}
}
//背景 星々
for( var i = this.stars.length - 1; i >= 0; i-- ) {
var star = this.stars[ i ];
star[ 0 ] -= 4;
//check
if( star[ 0 ] < 0 ) this.stars.splice( i, 1 );
}
if( this.a % 5 == 0 ) { this.addStar( this.canvasW ); }
this.draw();
this.a++;
};
app.colors = [ "#00f", "#888", "yellow" ];
//関数 9 / 31 ..
app.addStar = function( gx ) {
var color = this.colors[ Math.floor( Math.random() * this.colors.length ) ];
gx = Math.round( gx );
var gy = Math.round( Math.random() * this.canvasH );
this.stars.push( [ gx, gy, color ] );
}
//関数 10 / 31 ..
app.drawTextCenter = function( t ) {
var cc = this.cc;
var sz = 24;
var met = cc.measureText( t );
var x = ( this.canvasW - met.width ) / 2 - met.width / 2;
var y = ( this.canvasH - sz ) / 2;
cc.font = sz + "px ''";
cc.fillStyle = "darkblue"; cc.fillText( t, x+4,y+4 );
cc.fillStyle = "cyan"; cc.fillText( t, x-2,y );
cc.fillStyle = "cyan"; cc.fillText( t, x+1,y );
cc.fillStyle = "cyan"; cc.fillText( t, x,y-2 );
cc.fillStyle = "cyan"; cc.fillText( t, x,y+1 );
cc.fillStyle = "blue"; cc.fillText( t, x,y );
}
//関数 11 / 31 ..
app.draw = function() {
var cc = this.cc;
cc.clearRect( 0, 0, this.canvasW, this.canvasH );
if( this.drawFLG == "preload" ) {
this.drawTextCenter( "PRELOADING IMAGES.." );
return;
}
//☆彡 描画
for( var i = this.stars.length - 1; i >= 0; i-- ) {
var star = this.stars[ i ];
var gx = star[ 0 ];
var gy = star[ 1 ];
cc.fillStyle = star[ 2 ];
cc.fillRect( gx, gy, 1, 1 );
}
//スプライト 描画
for( var name in this.sprites ) {
var sprite = this.sprites[ name ];
sprite.draw( cc );
}
//地上 描画
this.maps.draw( cc );
cc.font = "18px ''";
cc.fillStyle = "white";
cc.fillText( "a: " + this.a + " sprites: " + Object.keys( this.sprites ).length, 50, 20 );
if( this.pause ) {
this.drawTextCenter( "PAUSE" );
}
};
//関数 12 / 31 ..
app.keyType = function( keynum ) {
switch( keynum ) {
case 90: this.fire(); break; //z
case 65: //a
this.stop();
this.toA = Number( prompt( "moveto:", this.a ) );
this.toAMode = true;
this.toABak = this.timerMS;
this.timerMS = 1;
this.a = 0;
this.start();
break;
case 80: this.pause = ! this.pause; break; //p
default:
// console.log( this.id + " key typed: " + keynum );
}
};
//関数 13 / 31 ..
app.keySense = function() {
//keySenseはプログラマーがrun()などから適宜呼ぶ
//サンプル
//キーテーブルをすべて処理
for( var i = 0; i < this.keytable.length; i++ ) {
var keynum = this.keytable[ i ];
switch( keynum ) {
case 37: this.fighter.x -= this.fighter.speed; break;
case 39: this.fighter.x += this.fighter.speed; break;
case 38: this.fighter.y -= this.fighter.speed; break;
case 40: this.fighter.y += this.fighter.speed; break;
default:
}
}
};
//関数 14 / 31 ..
app.fire = function() {
var missile = new Sprite( "sm" + this.supporterMissileCnt++, this );
with( missile ) {
type = this.SupporterMissileType;
imagePalette = new ImagePalette( this.cc, this.images.sm );
x = this.fighter.x + this.fighter.centerX - 8;
y = this.fighter.y + 4;
speed += 8;
setWidthWithAspectHeight( 8 );
frame = function() {
//"this" is missile.
this.x += this.speed;
this.deleteFlg = this.x > this.app.canvasW;
};
}
this.entrySprite( missile );
};
//---class
//関数 15 / 31 ..
function ImagePalette( cc, image, areaWidth, areaHeight, width, height ) {
this.cc = cc;
this.image = image;
if( arguments.length == 2 ) {
this.drawImage = this.drawImage_single;
this.width = this.image.width;
this.height = this.image.height;
} else {
this.areaWidth = areaWidth;
this.areaHeight = areaHeight;
this.width = width;
this.height = height;
this.columns = Math.floor( image.width / this.areaWidth );
this.rows = Math.floor( image.height / this.areaHeight );
this.maxIndex = this.columns * this.rows;
this.index = 0;
}
}
//関数 16 / 31 ..
ImagePalette.prototype.drawImage = function( dx, dy, dw, dh ) {
var sx = this.index * this.areaWidth;
this.cc.drawImage( this.image, sx, 0, this.width, this.height, dx, dy, dw, dh );
};
//関数 17 / 31 ..
ImagePalette.prototype.drawImage_single = function( dx, dy, dw, dh ) {
this.cc.drawImage( this.image, dx, dy, dw, dh );
};
//関数 18 / 31 ..
function Sprite( id, app ) {
this.app = app;
this.type = null;
this.id = id;
this.imagePalette = null;
this.x = 100;
this.y = 100;
this.setWidth( 32 );
this.setHeight( 32 );
this.speed = 8;
this.deleteFlg = false;
this.moveFrameCnt = 0;
this.moveFrameMax = 1;
this.animFrameCnt = 0;
this.animFrameMax = 1;
}
//関数 19 / 31 ..
Sprite.prototype.setWidth = function( width ) {
this.width = width;
this.centerX = width / 2;
};
//関数 20 / 31 ..
Sprite.prototype.setHeight = function( height ) {
this.height = height;
this.centerY = height /2;
}
//関数 21 / 31 ..
Sprite.prototype.setWidthWithAspectHeight = function( width ) {
this.setWidth( width );
this.setHeight( this.width / this.imagePalette.width * this.imagePalette.height );
};
//関数 22 / 31 ..
Sprite.prototype.draw = function( cc ) {
cc.save();
cc.translate( this.x - this.centerX, this.y - this.centerY );
this.imagePalette.drawImage( 0, 0, this.width, this.height );
cc.restore();
};
//関数 23 / 31 ..
Sprite.prototype.frame = function( cc ) {
};
//関数 24 / 31 ..
Sprite.prototype.moveFunction = function( cc ) {
this.x -= this.speed;
};
//関数 25 / 31 ..
Sprite.prototype.animFrameChecker = function( cc ) {
if( this.animFrameCnt++ == this.animFrameMax ) {
this.animFrameCnt = 0;
this.imagePalette.index ++;
//check.
if( this.imagePalette.index > this.imagePalette.maxIndex ) this.imagePalette.index = 0;
}
};
//関数 26 / 31 ..
Sprite.prototype.moveFrameChecker = function( isDelete ) {
if( this.moveFrameCnt++ == this.moveFrameMax ) {
this.moveFrameCnt = 0;
this.moveFunction();
this.deleteFlg = isDelete;
}
}
//関数 27 / 31 ..
function Maps( app ) {
this.app = app;
this.array = new Array();
this.visibles = new Array();
this.frameCnt = 0;
this.frameMax = 1;
this.index = 0;
this.movePixel = 4;
}
//関数 28 / 31 ..
Maps.prototype.ready = function( mapName ) {
this.array = new Array();
var nums = new Array();
for( var name in this.app.images ) {
if( name.indexOf( mapName ) == 0 ) {
var num = Number( name.substr( mapName.length ) );
nums.push( num );
}
}
nums.sort( function( a, b ) {
if( a < b ) return -1;
if( a > b ) return 1;
return 0;
} );
for( var i = 0; i < nums.length; i++ ) {
this.array.push( new Map( this.app.images[ mapName + nums[ i ] ] ) );
}
this.visibles.push( this.array[ 0 ] );
this.index = 0;
}
//関数 29 / 31 ..
Maps.prototype.frame = function() {
//check.
if( this.frameCnt++ != this.frameMax ) {
return;
}
this.frameCnt = 0;
var allEnd = true;
for( var i = 0; i < this.visibles.length; i++ ) {
var map = this.visibles[ i ];
//サイズを更新(ウィンドウリサイズを考慮して)
map.width = map.image.width * ( this.app.canvasH / map.image.height );
map.height = this.app.canvasH;
map.scrollX += this.movePixel;
map.endFlg = map.scrollX >= map.width + this.app.canvasW;
//check. 画面右端にマッチして、次のマップが必要か。
if( map == this.array[ this.index ] && map.scrollX >= map.width ) {
this.index++;
console.log( 321, this.index );
if( this.index < this.array.length ) {
this.visibles.push( this.array[ this.index ] );
}
}
if( map.endFlg == false ) {
allEnd = false;
}
}
if( allEnd ) {
this.visibles = new Array();
}
}
//関数 30 / 31 ..
Maps.prototype.draw = function( cc ) {
for( var i = 0; i < this.visibles.length; i++ ) {
var map = this.visibles[ i ];
cc.drawImage( map.image, this.app.canvasW - map.scrollX, 0, map.width, map.height );
}
}
//関数 31 / 31 ..
function Map( image ) {
this.scrollX = 0;
this.image = image;
this.endFlg = false;
this.width = 0;
this.height = 0;
}
//---/class
ファイル一式 (2MB)おてもとのPCで動作できます。
動作確認ブラウザ:
冒頭のお姉さんの画像(下図左)の髪型をどうにかしようと思って いじっていたんですが、やっぱり絵というのは「ソラ」(資料なし)では描けなくて、うまく描けませんでした。
そういう「描けない!」というときの手段として、やはり「資料を使う」という方法があります。
下図中央の写真は、あるテレビ番組のワンショットです。(肖像権とかあると思うので、おおざっぱにモザイクをかけてあります)
この写真を見ながら髪型を作ることにしました。
それでできたのが下図右です。下図左と比べてだいぶグッとくる絵になりました。
+ =
こうやって資料を基にしてたくさん描くと、それが身について、資料を見なくても描けるようになるんだと思います。
髪型のほか、顔の りんかく も「描けない!」と思っていたんですが、髪型がこのようにエレガントになると、つられて「りんかく」もキュートになるようにいじることができました。
この「つられて描ける」ようになることを、個人的に「ラブリーメソッドが はたらいた」と呼んでいます。
いわゆるノリのことで、ノッてきた~!と思うと、エンジンがかかって、ほかの部分も急にグレードが上がるんです。
たとえば、四角形をポンと置いて、その塗りつぶしを青から肌色に変えるとノッてきます。
人間キャラを描くんだぞ、という気になります。
適当に描いた目を、もっと魅力的に描きこむとノッてきます。
上図右に髪の毛を配置したら見栄えするだろうし、鼻あたりのりんかくを加えたら良くなりそうです。
このように「次」をうながす魅力的な何かを「ラブリーメソッド」と私は最近呼んでいます。絵を続けて描こうという気になるんです。
そういうわけで描きこんだ目に、下図のように図形を配置するだけで何かに見えませんか?
こんなふうに、描く気はなかったのにエンジンがかかるんです。
上図右の女の子は「コレを描こう!」と思ってできた絵ではなく、なりゆきのおもいつきで出来上がった絵です。
絵を描くとき、何か最初に注文があってその注文に応える形で描く絵と、何も注文がないところ思い付きで描く絵と2種類があると思います。
注文のとおりに描ければ、絵でご飯が食べていけると思います。私は誰かに頼まれて描く絵(注文に応える)というのがとても苦手です。
話は脱線しましたが、「資料」を使うほか、「ラブリーメソッド」(魅力的なパーツを投入する)を使うことでも、絵というのはエンジンがかかるんです。
(資料やラブリーメソッドだけで絵を描けるかどうかはわかりませんが、日ごろから絵について練習してみたり、研究することは必要です。必要ですと言われてやるんじゃなくて(やってもいいけど)、もしかしたらある程度誰かから褒められて絵を描く自分を作ってもらえること(そういう人生)が必要なのかもしれない。今絵が下手だからダメなんだではなく、その絵をバカにせず、才能の芽を見つけ出して褒めてくれる人がいると良い、のかなぁ。お世辞でほめるんじゃなくて、下手だとされるその絵の中で、ある部分が芸術として本当に有効だとわかって褒めてくれる人)
今週の木曜日のこと。
会社からの帰宅中、20:40ごろ。
暗がりに子猫らしい鳴き声が聞こえるので、あたりを見渡したら、生後数か月くらいの白黒模様の子猫がいました。
普通の猫は、人間が寄ると逃げますが、子猫だったせいか私が寄っても逃げませんでした。
この寒いのに…飼い猫であれば外に出るような気温じゃないはず。首輪はなくて野良猫らしい。
私がしゃがむと、ヒザに乗っかってきました。
そして私の肩によじ登り、リュックサックの上が平べったいタイプだったせいか、そこに陣取りました。おっとっと、おっとっと
通行人が二人くらい。
自宅に連れて帰るのはどう考えても無理だったので、誰かに引き取ってもらうことを考えましたが、それも無理でしょう。
このままずっと、というわけにもいかないので、立ち去るタイミングを探していたら、それがわかるらしく、
犬が飼い主にあまえるような感じで、立った私のヒザに両手で すがって「いかないで」。猫らしくないしぐさ。よっぽど一人でいるのがつらいみたい。
年の瀬で飲み会帰りのビジネスマンが車で送ってもらって降りてきて、それに気を取られているすきを狙って私は立ち去りましたが、
私の姿が見えなくなったせいか、また一人になったせいか、その鳴き声がかなしげ。
私は自宅に向かって帰る途中、何かにギューっと押しつぶされたようなやりきれない気持ちになりました。その気持ちがだいぶ強力でした。
まったく悲しくはありませんが、心が押しつぶされる感覚が非常に強かった。これでもかと縄で心を縛り付け、頭に来ながら我慢するような感じ。
もちろん子猫に対して頭に来ているのではなく、何もできなくて置き去りにするしかない状況に対して。
自宅に着いて玄関に立ち、そのまま10分くらい動けず、結局コンビニへ行ってキャットフード(にくまんなど人間の食べ物をあげるのはおなか壊すし糖尿病にもなるので不可で、カルカンが研究しつくされているのでカルカンが良い)を買い、再びその場所へ行くと猫の姿はありませんでした。
よくよく考えると、今年の6月ごろ、同じ白黒模様のおなかの大きな妊娠中の猫を見た気がします。ガラが同じなのでその生まれた子供だと思います。
12/14(金)は、「あっ見えた!感動~」
ふたご座流星群が見られるというので、部屋から外に出て空を見上げました。
5分ほど見上げていると、スイーッと1つ流れ星が見えました。
たくさん見られると言うので、その後も見ようとしましたが、首が痛くなってきたのであきらめました。
1つ見られれば十分。
22時ごろ見ましたが、一番良いのは21時ごろらしいです。
最近やっているプログラミングは下図のようなもの。
Microsoft ExcelのVBAで、「関節が稼働するデッサン人形みたいなもの」(私がいつも言っているSVC)を作成するソフトウェアを作っています。
今まではVBAの「フォーム」という方法で操作画面を作っていましたが、Windowsの「リボン」が使えることを知って、「リボン」へと移行しています。
「フォーム」は下図のように Excelの画面上に浮いていて、作業エリアを覆ってしまうので作業しづらいですが、
「リボン」はリボンのエリアに収まるので、作業エリアが覆われず、すっきり!
▼こんな画面でプログラミングしています。
▼過去に作ったSVC画像。
SVCは、関節をクルクル動かすだけの簡単操作なのに、上図のように結構 表現力が豊かです。しかし、「SVCは」というより、絵を描くこと自体がもともとそういうものなのかもしれません。絵を教えるとき、誰でも必ず「最初は単純な図形でとらえましょう」と言うし、その単純な図形を関節で曲げることを考えれば、それはSVCでやってることと同じになります。
VBAのコード (作り途中のものですが、興味のある方はクリックして表示してください)
''- 関数リスト -
'' 1: ShapeEX_onload
'' 2: checkBox_getPressed
'' 3: ribbon10a
'' 4: getBlankLeft
'' 5: swapConnectors
'' 6: ribbon10a_
'' 7: afterShapesMoveBy
'' 8: getConnectorBefore
'' 9: getConnectorsAfter
'' 10: ribbon16
'' 11: ribbon12
'' 12: ribbon13
'' 13: ribbon14
'' 14: ribbon15
'' 15: ribbon11
'' 16: ribbon8a
'' 17: ribbon8b
'' 18: ribbon8c
'' 19: ribbon8d
'' 20: SpinButton1_SpinSub
'' 21: kaitenShapes
'' 22: ribbon10
'' 23: treeMoveBy
'' 24: kaitenTree
'' 25: kaitenShape
'' 26: kaiten2
'' 27: ribbon9
'' 28: ribbon8
'' 29: Collection_indexOf
'' 30: ribbon7
'' 31: makeConnector
'' 32: Collection_existsByName
'' 33: ribbon6
'' 34: ribbon5
'' 35: ribbon4
'' 36: ribbon3
'' 37: selectionIsError
'' 38: alert
'' 39: alert_click
'' 40: getSvcsBefore
'' 41: Collection_concat
'' 42: getSvcsAfter
'' 43: searchConnectorsOnArm
'' 44: ButtonSelectConnectors_Click
'' 45: updateConnectorVisible
'' 46: ButtonSelectAll_Click
'' 47: getAllSvcs
'' 48: getAllArms
'' 49: getAllConnectors
'' 50: searchUTL
'' 51: searchShapesByKey
'' 52: connectorName2arms
'名称:
'すべての図形 図形 shape
'図形と図形を結ぶ意味を持つ図形 コネクタ connector
'コネクタに連なる図形 アーム arm
'コネクタとアーム svc svc
Option Explicit
Private acShapes As Shapes
Private sw_connectorVisible As Boolean
Private shapeEX_alert As Shape
Private all As Collection
Private connectors As Collection
Private arms As Collection
Private tree As TreeObjectClass
Private noParents As Collection
''関数 1 / 52
Public Sub ShapeEX_onload(Optional ribbonControl As IRibbonControl = Null)
Debug.Print "ShapeEX_onload"
Set acShapes = ActiveSheet.Shapes
sw_connectorVisible = True
End Sub
'チェックボックス初期値
''関数 2 / 52
Sub checkBox_getPressed(ribbonControl As IRibbonControl, ByRef returnValue)
Select Case ribbonControl.ID
Case "checkBox1"
returnValue = True
End Select
End Sub
''関数 3 / 52
Private Sub ribbon10a(ribbonControl As IRibbonControl)
'check
If selectionIsError(False, True, True, 2) Then Exit Sub
Dim connector As Shape
'アームが選択されていたらコネクタを選択しなおす。
Dim orgSelection As Collection
Set orgSelection = New Collection
Dim newSelection As Collection
Set newSelection = New Collection
Dim arms As Collection
Set arms = getAllArms
Dim connectors As Collection
Set connectors = getAllConnectors
'各選択について
Dim theShape As Shape
For Each theShape In Selection.ShapeRange
orgSelection.add theShape
If Collection_indexOf(arms, theShape) > -1 Then
'選択はアームである
'check. アームに続くコネクタが1つあり、そのコネクタは子を持たない場合について
Dim connectorsAfter As Collection
Set connectorsAfter = getConnectorsAfter(theShape)
If connectorsAfter.Count = 1 Then
If connectorsAfter.item(1).name Like "*>" Then
newSelection.add connectorsAfter.item(1)
GoTo continue
End If
End If
Set connector = getConnectorBefore(theShape)
'check. トップアームにはコネクタがないので
If connector Is Nothing Then
MsgBox "this is top."
Exit Sub
End If
newSelection.add connector
ElseIf Collection_indexOf(connectors, theShape) > -1 Then
'選択はコネクタである
newSelection.add theShape
Else
'選択はその他である(アームでもコネクタでもない図形)
newSelection.add makeConnector(theShape, False)
End If
continue:
Next theShape
'以降コネクタを対象にした処理
'選択状態の分析
Dim theName As String
Dim countOfHasParent As Integer
countOfHasParent = 0
Dim countOfHasChild As Integer
countOfHasChild = 0
Dim countOfHasNoChild As Integer
countOfHasNoChild = 0
For Each theShape In newSelection
theName = theShape.name
If theName Like "*>" Then
countOfHasParent = countOfHasParent + 1
countOfHasNoChild = countOfHasNoChild + 1
ElseIf theName Like ">*" Then
countOfHasChild = countOfHasChild + 1
ElseIf theName Like "*>*" Then
countOfHasParent = countOfHasParent + 1
countOfHasChild = countOfHasChild + 1
Else
'ここにくることはないはず
MsgBox "err"
End If
Next theShape
Dim fromConnector As Shape
Dim toConnector As Shape
Application.ScreenUpdating = False
If newSelection.Count = 1 Then
'そのコネクタは子を持っているか
theName = newSelection.item(1).name
If theName Like "*>" Then
'子を持っていない(手など)
'手に持っていないところ、何か手に持つ。
Debug.Print "1 手に持っていないところ、何か手に持つ。"
ElseIf theName Like ">*" Then
'子を持っている、親を持っていない(手放したアイテムなど)
Debug.Print "2 その物を両手どちらかに持たせる"
Else
'子を持っている、親を持っている(手にアイテムを持っている)
Debug.Print "3 持っているものを手放す"
'持ち物
Set fromConnector = newSelection.item(1)
'ダミー位置
Set toConnector = getAllConnectors.item(1)
toConnector.Copy
ActiveSheet.Paste
Set toConnector = Selection.ShapeRange.item(1)
toConnector.name = ">"
toConnector.left = getBlankLeft(fromConnector)
toConnector.top = fromConnector.top
toConnector.Rotation = 0
swapConnectors fromConnector, toConnector
orgSelection.item(1).Select False
End If
ElseIf Selection.ShapeRange.Count = 2 Then
If countOfHasChild = 2 Then
Debug.Print "4 通常スワップ"
ElseIf countOfHasChild = 1 And countOfHasNoChild = 1 Then
If countOfHasParent = 2 Then
Debug.Print "5 持ち替える"
Else
Debug.Print "6 手に何かを持つ"
swapConnectors newSelection.item(1), newSelection.item(2)
If newSelection.item(1).name Like "*>" Then
newSelection.item(1).Delete
orgSelection.item(2).Select
ElseIf newSelection.item(2).name Like "*>" Then
newSelection.item(2).Delete
orgSelection.item(1).Select
End If
End If
Else
Debug.Print "両方とも手"
End If
End If
Application.ScreenUpdating = True
End Sub
''関数 4 / 52
Private Function getBlankLeft(targetShape) As Integer
'アイテムを置く位置を検索
Dim toCenterX As Integer
toCenterX = targetShape.left + targetShape.Width / 2 + 200
Dim toCenterY As Integer
toCenterY = targetShape.top + targetShape.Height / 2
'check.
Dim theShape As Shape
Dim shapeCenterX As Integer
Dim shapeCenterY As Integer
Dim tole As Integer: tole = 20
Dim thereIsSameLeft As Boolean
Do
continue:
thereIsSameLeft = False
For Each theShape In acShapes
shapeCenterX = theShape.left + theShape.Width / 2
shapeCenterY = theShape.top + theShape.Height / 2
If toCenterX > shapeCenterX - tole And toCenterX < shapeCenterX + tole And toCenterY > shapeCenterY - tole And toCenterY < shapeCenterY + tole Then
Debug.Print "近い", theShape.name
thereIsSameLeft = True
toCenterX = toCenterX + 100
GoTo continue
End If
Next theShape
Loop While thereIsSameLeft
getBlankLeft = toCenterX - targetShape.Width / 2
End Function
''関数 5 / 52
Private Sub swapConnectors(connector1 As Shape, connector2 As Shape)
Debug.Print "swap", connector1.name, connector2.name
'コネクタ1と2の距離
Dim diffLeft As Integer, diffTop As Integer
Dim centerLeft1 As Integer, centerTop1 As Integer
Dim centerLeft2 As Integer, centerTop2 As Integer
centerLeft1 = connector1.left + connector1.Width / 2
centerTop1 = connector1.top + connector1.Height / 2
centerLeft2 = connector2.left + connector2.Width / 2
centerTop2 = connector2.top + connector2.Height / 2
diffLeft = centerLeft1 - centerLeft2
diffTop = centerTop1 - centerTop2
Dim tokens1 As Variant
tokens1 = Split(connector1.name, ">")
Dim tokens2 As Variant
tokens2 = Split(connector2.name, ">")
'コネクタ2と連なるものの位置移動
Dim theShape As Shape
Dim afterShapes1 As Collection
Dim firstshape1 As Shape
Dim afterShapes2 As Collection
Dim firstshape2 As Shape
Set afterShapes2 = getSvcsAfter(connector2)
If afterShapes2.Count = 1 Then
Set firstshape2 = afterShapes2.item(1)
Else
Set firstshape2 = afterShapes2.item(2)
End If
For Each theShape In afterShapes2
theShape.left = theShape.left + diffLeft
theShape.top = theShape.top + diffTop
Next theShape
connector2.Select
connector1.name = tokens2(0) & ">" & tokens1(1)
connector2.name = tokens1(0) & ">" & tokens2(1)
'コネクタ1と連なるものの位置移動
Set afterShapes1 = getSvcsAfter(connector1)
If afterShapes1.Count = 1 Then
Set firstshape1 = afterShapes1.item(1)
Else
Set firstshape1 = afterShapes1.item(2)
End If
For Each theShape In afterShapes1
theShape.left = theShape.left - diffLeft
theShape.top = theShape.top - diffTop
Next theShape
'回転
Dim diffRotation As Double
diffRotation = firstshape1.Rotation - firstshape2.Rotation
Dim diffTheta As Double
diffTheta = WorksheetFunction.Radians(diffRotation)
kaitenShapes firstshape1, -diffTheta, True
kaitenShapes firstshape2, diffTheta, True
End Sub
''関数 6 / 52
Private Sub ribbon10a_(ribbonControl As IRibbonControl)
Dim arms As Collection
Set arms = getAllArms
'check.
' If Collection_indexOf(arms, arm1) = -1 Or Collection_indexOf(arms, arm2) = -1 Then
' MsgBox "両方ともアームでなくてはなりません"
' Exit Sub
' End If
'備考
'この関数はまともに動いているが、コードがあまりきれいじゃない。
'準備
Dim arm1 As Shape
Dim connector1 As Shape
Dim arm2 As Shape
Dim connector2 As Shape
If Selection.ShapeRange.Count = 1 Then
Set shape1 = Selection.ShapeRange.item(1)
If shape1.name Like "*>*" Then
Set arm1 = Nothing
Set connector1 = shape1
ElseIf Collection_indexOf(arms, shape1) > -1 Then
Set arm1 = shape1
Set connector1 = getConnectorBefore(arm1)
End If
ElseIf Selection.ShapeRange.Count = 2 Then
Set arm1 = Selection.ShapeRange.item(1)
Set arm2 = Selection.ShapeRange.item(2)
Else
End If
Dim afterShapes1 As Collection
Dim firstshape1 As Shape
Dim afterShapes2 As Collection
Dim firstshape2 As Shape
Dim theShape As Shape
'コネクタ1について
Set arm1 = Selection.ShapeRange.item(1)
Set connector1 = getConnectorBefore(arm1)
Dim tokens1 As Variant
tokens1 = Split(connector1.name, ">")
'check 選択が1つ(コネクタ1で持っているアイテムを手放す)「→」
If Selection.ShapeRange.Count = 1 Then
'位置を交換する相手がないので、アイテムを置く位置を検索
Dim blankLeft As Integer
blankLeft = 200
'check.
Dim t As Integer: t = 16
Dim thereIsSameLeft As Boolean
Dim theLeft As Integer, theTop As Integer
Do
continue:
thereIsSameLeft = False
For Each theShape In acShapes
theLeft = connector1.left + blankLeft
theTop = connector1.top
If theLeft > theShape.left - t And theLeft < theShape.left + t And theTop > theShape.top - t And theTop < theShape.top + t Then
thereIsSameLeft = True
blankLeft = blankLeft + 100
GoTo continue
End If
Next theShape
Loop While thereIsSameLeft
afterShapesMoveBy connector1, blankLeft, 0
connector1.name = ">" & tokens1(1)
acShapes(tokens1(0)).Select
ButtonNewConnectorAt_Click
'ButtonUpdate2newInfo_Click
'ListBoxNoParentShapes.Selected(ListBoxSearchIndexOf(userform1.ListBoxNoParentShapes, connector1.name)) = True
Set afterShapes1 = getSvcsAfter(connector1)
Set firstshape1 = afterShapes1.item(2)
kaitenShapes connector1, -WorksheetFunction.Radians(firstshape1.Rotation), True
Exit Sub
End If
'コネクタ2について
Set arm2 = Selection.ShapeRange.item(2)
Set connector2 = getConnectorBefore(arm2)
Dim tokens2 As Variant
tokens2 = Split(connector2.name, ">")
'コネクタ1と2の距離
Dim diffLeft As Integer, diffTop As Integer
Dim centerLeft1 As Integer, centerTop1 As Integer
Dim centerLeft2 As Integer, centerTop2 As Integer
centerLeft1 = connector1.left + connector1.Width / 2
centerTop1 = connector1.top + connector1.Height / 2
centerLeft2 = connector2.left + connector2.Width / 2
centerTop2 = connector2.top + connector2.Height / 2
diffLeft = centerLeft1 - centerLeft2
diffTop = centerTop1 - centerTop2
'コネクタ2と連なるものの位置移動(手に持つ)「←」
Set afterShapes2 = getSvcsAfter(connector2)
Set firstshape2 = afterShapes2.item(2)
For Each theShape In afterShapes2
theShape.left = theShape.left + diffLeft
theShape.top = theShape.top + diffTop
Next theShape
Debug.Print "z2", firstshape2.ZOrderPosition
Debug.Print "z1", acShapes(tokens1(0)).ZOrderPosition
connector2.Select
connector2.name = tokens1(0) & ">" & tokens2(1)
If Not tokens1(1) = "" Then connector1.name = ">" & tokens1(1)
If connector1.name Like "*>" Then
'最初何も持っていなかった場合(コネクタ2その他が移動するだけで、終了)
connector1.Delete
'リストボックスを選択しておく
' ListBoxNoParentShapes.Selected(0) = True
Else
'最初何か持っていた場合(コネクタ1と2は位置を交換)「→←」
'コネクタ1と連なるものの位置移動
Set afterShapes1 = getSvcsAfter(connector1)
Set firstshape1 = afterShapes1.item(2)
For Each theShape In afterShapes1
theShape.left = theShape.left - diffLeft
theShape.top = theShape.top - diffTop
Next theShape
'回転
Dim diffRotation As Double
diffRotation = firstshape1.Rotation - firstshape2.Rotation
Dim diffTheta As Double
diffTheta = WorksheetFunction.Radians(diffRotation)
kaitenShapes firstshape1, -diffTheta, True
kaitenShapes firstshape2, diffTheta, True
'リストボックスを選択しておく
' ListBoxNoParentShapes.Selected(ListBoxSearchIndexOf(userform1.ListBoxNoParentShapes, connector1.name)) = True
End If
End Sub
''関数 7 / 52
Private Sub afterShapesMoveBy(targetShape As Shape, addLeft As Integer, addTop As Integer)
Dim theShapes As Collection
Set theShapes = getSvcsAfter(targetShape)
Dim theShape As Shape
For Each theShape In theShapes
theShape.left = theShape.left + addLeft
theShape.top = theShape.top + addTop
Next theShape
End Sub
'そのアームがつながるコネクタを得る
''関数 8 / 52
Private Function getConnectorBefore(arm As Shape) As Shape
Dim theShapes As Collection
Set theShapes = searchShapesByKey("*>" & arm.name)
If theShapes.Count = 0 Then
Set getConnectorBefore = Nothing
ElseIf theShapes.Count = 1 Then
Set getConnectorBefore = theShapes.item(1)
Else
'エラー ツリーの枝が合流してしまっている
Set getConnectorBefore = Null
End If
End Function
'そのアームにつながるコネクタを得る
''関数 9 / 52
Private Function getConnectorsAfter(arm As Shape) As Collection
Set getConnectorsAfter = searchShapesByKey(arm.name & ">*")
End Function
'ボタン 選択図形(複数可)を 角度 0°にする(ツリーを無視して)
''関数 10 / 52
Private Sub ribbon16(ribbonControl As IRibbonControl) 'ButtonSetRotation0_Click
Dim theShape As Shape
For Each theShape In Selection.ShapeRange
theShape.Rotation = 0
Next
End Sub
'最前面
''関数 11 / 52
Private Sub ribbon12(ribbonControl As IRibbonControl) 'ButtonToMostFront_Click
'check.
If selectionIsError(False, True, True) Then Exit Sub
Selection.ShapeRange.ZOrder msoBringToFront
End Sub
'前面
''関数 12 / 52
Private Sub ribbon13(ribbonControl As IRibbonControl) 'ButtonToFront_Click
'check.
If selectionIsError(False, True, True) Then Exit Sub
Selection.ShapeRange.ZOrder msoBringForward
End Sub
'背面
''関数 13 / 52
Private Sub ribbon14(ribbonControl As IRibbonControl) 'ButtonToRear_Click
'check.
If selectionIsError(False, True, True) Then Exit Sub
Selection.ShapeRange.ZOrder msoSendBackward
End Sub
'最背面
''関数 14 / 52
Private Sub ribbon15(ribbonControl As IRibbonControl) 'ButtonToMostRear_Click
'check.
If selectionIsError(False, True, True) Then Exit Sub
Selection.ShapeRange.ZOrder msoSendToBack
End Sub
'ボタン 選択図形の枠線を2重線にする
'2重線は、「以前を回転」する場合に、回転の中心のコネクタを決める処理で、「2重線のコネクタ」を優先する。
'たとえば、胸以前を回転する場合、胸には腹、左腕、右腕のコネクタがあり、左腕のコネクタを中心に回転してしまうと意図した回転にならない
'腹のコネクタの枠線を2重線にすれば、2重線を頼りに腹を優先するようになる。腹のコネクタを中心に回転すれば意図した回転になる。
''関数 15 / 52
Private Sub ribbon11(ribbonControl As IRibbonControl) 'ButtonSetLineStyle2thinthin_Click
Dim connector As Shape
For Each connector In Selection.ShapeRange
connector.line.Style = msoLineThinThin
connector.line.Weight = 3
connector.line.ForeColor.RGB = connector.Fill.ForeColor.RGB
connector.line.Transparency = connector.Fill.Transparency
connector.line.Visible = True
Next connector
End Sub
'以前を左回転
''関数 16 / 52
Private Sub ribbon8a(ribbonControl As IRibbonControl) 'SpinButtonBefore_Spindown
'check
If selectionIsError(False, True, False) Then Exit Sub
SpinButton1_SpinSub -0.02, False 'booleanはorder
End Sub
'以前を右回転
''関数 17 / 52
Private Sub ribbon8b(ribbonControl As IRibbonControl) 'SpinButtonBefore_Spinup
SpinButton1_SpinSub 0.02, False 'booleanはorder
End Sub
'以後を左回転
''関数 18 / 52
Private Sub ribbon8c(ribbonControl As IRibbonControl) 'SpinButtonAfter_Spindown
'check
If selectionIsError(False, True, False) Then Exit Sub
SpinButton1_SpinSub -0.02, True 'booleanはorder
End Sub
'以後を右回転
''関数 19 / 52
Private Sub ribbon8d(ribbonControl As IRibbonControl) 'SpinButtonAfter_Spinup
'check
If selectionIsError(False, True, False) Then Exit Sub
SpinButton1_SpinSub 0.02, True 'booleanはorder
End Sub
'スピンボタン サブルーチン
''関数 20 / 52
Private Sub SpinButton1_SpinSub(theta2add As Double, order As Boolean)
'check
If selectionIsError(False, True, False) Then Exit Sub
'図形を取得
Dim targetSvc As Shape
Set targetSvc = ActiveSheet.Shapes(Selection.name)
kaitenShapes targetSvc, theta2add, order
End Sub
''関数 21 / 52
Private Sub kaitenShapes(targetSvc As Shape, theta2add As Double, order As Boolean)
'回転の中心
Dim connectors As Collection
Dim centerSvc As Shape
Set connectors = searchConnectorsOnArm(targetSvc, Not order) '5
If connectors.Count = 0 Then
Set centerSvc = targetSvc
Else
Set centerSvc = connectors.item(1)
'もし、枠線がmsoLineThinThinであるコネクタがあるならそれを採用
Dim connector As Shape
For Each connector In connectors
If connector.line.Style = msoLineThinThin Then Set centerSvc = connector
Next connector
End If
Dim svcs As Collection
If order Then
Set svcs = getSvcsAfter(targetSvc)
Else
Set svcs = getSvcsBefore(targetSvc, "[" & centerSvc.name & "]")
End If
Dim i As Integer
For i = 1 To svcs.Count
kaitenShape centerSvc, svcs.item(i), theta2add
Next i
End Sub
'ボタン 選択図形(1つ)を ツリーとして 角度 0°にする
'このプログラム中の順方向の補正距離orderDX,Yは使用していないが、
'使用する可能性があるので、取っておく
''関数 22 / 52
Private Sub ribbon10(ribbonControl As IRibbonControl) 'ButtonSetTreeRotation0_Click
'選択図形
Dim svc As Shape
Set svc = ActiveSheet.Shapes(Selection.name)
'選択図形の中心
Dim centerX As Double, centerY As Double
centerX = svc.left + svc.Width / 2
centerY = svc.top + svc.Height / 2
Dim theta2 As Double
theta2 = WorksheetFunction.Radians(svc.Rotation)
'雑用
Dim connectors As Collection
Dim connector As Shape
Dim connectorX As Double, connectorY As Double
Dim kaitenConnector As Collection
'--不要かもしれない ここから
'順方向の補正距離
Dim orderDX As Double, orderDY As Double
orderDX = 0
orderDY = 0
'選択図形上の順方向コネクタを取得
Set connectors = searchConnectorsOnArm(svc, True) '1
'check 順方向コネクタあるなら
If connectors.Count > 0 Then
Set connector = connectors.item(1)
'コネクタの中心
connectorX = connector.left + connector.Width / 2
connectorY = connector.top + connector.Height / 2
Set kaitenConnector = kaiten2(centerX, centerY, connectorX, connectorY, -theta2)
orderDX = kaitenConnector("x") - connectorX
orderDY = kaitenConnector("y") - connectorY
End If
'--不要かもしれない ここまで
'逆方向の補正距離
Dim reverseDX As Double, reverseDY As Double
reverseDX = 0
reverseDY = 0
'選択図形上逆方向コネクタを取得
Set connectors = searchConnectorsOnArm(svc, False) '1
'check 逆方向コネクタあるなら
If connectors.Count > 0 Then
Set connector = connectors.item(1)
'コネクタの中心
connectorX = connector.left + connector.Width / 2
connectorY = connector.top + connector.Height / 2
Set kaitenConnector = kaiten2(centerX, centerY, connectorX, connectorY, -theta2)
reverseDX = kaitenConnector("x") - connectorX
reverseDY = kaitenConnector("y") - connectorY
End If
kaitenTree Nothing, svc, -theta2
treeMoveBy svc, -reverseDX, -reverseDY
End Sub
''関数 23 / 52
Sub treeMoveBy(targetSvc As Shape, x As Double, y As Double)
'treeMoveByは現在、プログラム中1か所から呼ばれている。(ツリーを考慮した回転0°)
targetSvc.left = targetSvc.left + x
targetSvc.top = targetSvc.top + y
Dim connectors As Collection
Set connectors = searchConnectorsOnArm(targetSvc, True) '6
Dim connector As Shape
For Each connector In connectors
connector.left = connector.left + x
connector.top = connector.top + y
Dim pair As Collection
Set pair = connectorName2arms(connector.name)
treeMoveBy pair("child"), x, y
Next connector
End Sub
'3段階の回転
'kaitenTree ツリー構造全体を回転
'kaitenShape shapeを回転
'kaiten2 座標を回転
'コネクタを中心に、targetSvcを回転する。連なる子も回転する。
''関数 24 / 52
Sub kaitenTree(centerSvc As Shape, targetSvc As Shape, theta2 As Double)
'kaitenTreeは現在、プログラム中1か所から呼ばれている。(ツリーを考慮した回転0°)
'centerSvcはその一か所でNothingが指定されている。
'自身の回転処理
If centerSvc Is Nothing Then
Set centerSvc = targetSvc
End If
kaitenShape centerSvc, targetSvc, theta2
'子の回転処理
Dim connectors As Collection
Set connectors = searchConnectorsOnArm(targetSvc, True) '7
Dim connector As Shape
For Each connector In connectors
'コネクタを回転
kaitenShape centerSvc, connector, theta2
'コネクタ以降のツリーを回転
Dim pair As Collection
Set pair = connectorName2arms(connector.name)
kaitenTree centerSvc, pair("child"), theta2
Next connector
End Sub
'kaiten2のshape対応版
''関数 25 / 52
Sub kaitenShape(centerShape As Shape, targetShape As Shape, theta2 As Double)
Dim cx As Double
Dim cy As Double
Dim x As Double
Dim y As Double
'中心点と、回転対象座標を得る
cx = centerShape.left + centerShape.Width / 2
cy = centerShape.top + centerShape.Height / 2
x = targetShape.left + targetShape.Width / 2
y = targetShape.top + targetShape.Height / 2
'回転する
Dim result As Collection
Set result = kaiten2(cx, cy, x, y, theta2)
'回転値をshapeに反映
targetShape.left = result("x") - targetShape.Width / 2
targetShape.top = result("y") - targetShape.Height / 2
targetShape.Rotation = targetShape.Rotation + WorksheetFunction.Degrees(theta2)
End Sub
''関数 26 / 52
Function kaiten2(ByVal cx As Double, ByVal cy As Double, ByVal x As Double, ByVal y As Double, ByVal theta2 As Double) As Collection
x = x - cx
y = y - cy
Dim theta1 As Double
Dim hankei As Double
If x = 0 And y = 0 Then
theta1 = 0
Else
theta1 = WorksheetFunction.Atan2(x, y)
End If
hankei = Sqr(x * x + y * y)
Set kaiten2 = New Collection
kaiten2.add Cos(theta1 + theta2) * hankei + cx, "x"
kaiten2.add Sin(theta1 + theta2) * hankei + cy, "y"
End Function
'名前維持してグループ解除
''関数 27 / 52
Private Sub ribbon9(ribbonControl As IRibbonControl) 'ButtonUngroup_click
'すべての図形の名前リスト
Dim shapesList As String
shapesList = ","
Dim theShape As Shape
For Each theShape In acShapes
shapesList = shapesList & theShape.name & ","
Next theShape
'グループの名前
Dim theName As String
theName = Selection.ShapeRange.name
'グループ解除
Selection.ShapeRange.Ungroup
Dim first As Boolean
first = True
Dim processed As Boolean
processed = False
For Each theShape In acShapes
'グループ解除で新たに表れた図形であるか
If Not shapesList Like "*," & theShape.name & ",*" Then
'名前が過去にグループ化されたSVCであるときは
If theShape.name Like "old_" & theName Then
'その名前を元に戻す
theShape.name = theName
processed = True
End If
'グループ解除したものをすべて選択状態にする
If first Then
theShape.Select
first = False
Else
theShape.Select False
End If
End If
Next theShape
'グループ解除を取り消し
If Not processed Then
Selection.ShapeRange.Group.Select
Selection.name = theName
alert "グループ解除はしませんでした", "「old_*」という名前の子に名前を引き継がせたいが、いないため。"
End If
End Sub
'名前を維持してグループ化
''関数 28 / 52
Private Sub ribbon8(ribbonControl As IRibbonControl) 'ButtonGroup_Click
Dim allSvcs As Collection
Set allSvcs = getAllSvcs
'複数選択のうちSVCであるものをリストアップ
Dim selectedSvcs As Collection
Set selectedSvcs = New Collection
Dim theShape As Shape
For Each theShape In Selection.ShapeRange
If Not Collection_indexOf(allSvcs, theShape) = -1 Then
selectedSvcs.add theShape
End If
Next theShape
'複数選択のうちSVCであるものが1つであるとき
If selectedSvcs.Count = 1 Then
'名前を維持してグループ化
Dim theName As String
theName = selectedSvcs.item(1).name
selectedSvcs.item(1).name = "old_" & selectedSvcs.item(1).name
Selection.ShapeRange.Group.Select
Selection.name = theName
alert "名前を維持してグループ化しました", "", rgbBlack
Else
'複数選択のうちSVCがない、またはSVCが2個以上のとき
'通常のグループ化
Selection.ShapeRange.Group.Select
alert "通常のグループ化をしました", "", rgbBlack
End If
End Sub
''関数 29 / 52
Private Function Collection_indexOf(theCollection As Collection, target As Object) As Integer
Dim theItem As Object
Dim idx As Integer
idx = 0
For Each theItem In theCollection
idx = idx + 1
If theItem Is target Then
Collection_indexOf = idx
Exit Function
End If
Next theItem
Collection_indexOf = -1
End Function
'新規コネクタ
''関数 30 / 52
Private Sub ribbon7(ribbonControl As IRibbonControl) 'ButtonNewConnectorAt_Click
'check
If selectionIsError(False, True, False) Then Exit Sub
makeConnector Selection.ShapeRange.item(1)
End Sub
''関数 31 / 52
Private Function makeConnector(arm As Shape, Optional typeIsAfter As Boolean = True) As Shape
Dim connectorName As String
If typeIsAfter Then
connectorName = arm.name & ">"
Else
connectorName = ">" & arm.name
End If
'check. すでにある
If Collection_existsByName(acShapes, connectorName) Then
acShapes(connectorName).Select
Exit Function
End If
Dim centerX As Integer
centerX = arm.left + arm.Width / 2
Dim centerY As Integer
centerY = arm.top + arm.Height / 2
Dim connector As Shape
Set connector = getAllConnectors.item(1)
connector.Copy
ActiveSheet.Paste
Selection.name = connectorName
Set makeConnector = acShapes(Selection.name)
makeConnector.left = centerX - connector.Width / 2
makeConnector.top = centerY - connector.Height / 2
End Function
''関数 32 / 52
Public Function Collection_existsByName(theCollection As Object, keyName As String) As Boolean
Dim theShape As Shape
For Each theShape In theCollection
If theShape.name Like keyName Then
Collection_existsByName = True
Exit Function
End If
Next theShape
Collection_existsByName = False
End Function
''関数 33 / 52
Private Sub ribbon6(ribbonControl As IRibbonControl) 'ButtonSelectorWindow_Click
userform3.Show 0
userform3.left = Application.Width - userform3.Width - 32
userform3.top = Application.Height / 2 - userform3.Height / 2
End Sub
'ボタン コネクタの表示非表示
''関数 34 / 52
Private Sub ribbon5(ribbonControl As IRibbonControl, pressed As Boolean) 'ButtonToggleConnectorVisiblity_Click
sw_connectorVisible = Not sw_connectorVisible
updateConnectorVisible
End Sub
'以降を選択
''関数 35 / 52
Private Sub ribbon4(ribbonControl As IRibbonControl) 'CommandButton_順_Click
'check
If selectionIsError(False, True, False) Then Exit Sub
Dim targetSvc As Shape
Set targetSvc = ActiveSheet.Shapes(Selection.name)
Dim svcs As Collection
Set svcs = getSvcsAfter(targetSvc)
Dim svc As Shape
For Each svc In svcs
svc.Select Replace:=False
Next svc
End Sub
'以前を選択
''関数 36 / 52
Private Sub ribbon3(ribbonControl As IRibbonControl) 'CommandButton_逆_Click
'check
If selectionIsError(False, True, False) Then Exit Sub
Dim targetSvc As Shape
Set targetSvc = ActiveSheet.Shapes(Selection.name)
Dim svcs As Collection
Set svcs = getSvcsBefore(targetSvc)
Dim svc As Shape
For Each svc In svcs
svc.Select Replace:=False
Next svc
End Sub
''関数 37 / 52
Public Function selectionIsError(zeroSelOK, oneSelOK, multiSelOK, Optional multiselmax As Integer = -1)
'選択状態が正しい時、falseを返す。
If Not zeroSelOK And TypeName(Selection) = "Range" Then
alert "何も選択されていません", "何か図形を選択してください"
selectionIsError = True
Exit Function
ElseIf Not multiSelOK And Selection.ShapeRange.Count > 1 Then
alert "この機能は単数選択のみです", "選択を1つにしてください"
selectionIsError = True
Exit Function
ElseIf multiSelOK And Selection.ShapeRange.Count > multiselmax Then
alert "この機能は複数選択可能ですが、選択は " & multiselmax & " 個までです。", "選択数を減らして下さい"
selectionIsError = True
Exit Function
End If
selectionIsError = False
End Function
''関数 38 / 52
Sub alert(messstr As String, Optional waystr As String = "", Optional messcol As OLE_COLOR = rgbRed, Optional waycol As OLE_COLOR = rgbGreen)
'check.
If Not Collection_existsByName(acShapes, "shapeEX_alert") Then
'アラート表示を作成
With ActiveSheet.Shapes.AddShape( _
Type:=msoShapeRectangle, _
left:=100, top:=100, Width:=200, Height:=125 _
)
.name = "shapeEX_alert"
.line.ForeColor.RGB = rgbBlack '枠線の色
.Fill.ForeColor.RGB = rgbLightSalmon '塗りつぶし色
.TextFrame2.WordWrap = True '右端で自動改行する
.OnAction = "alert_click"
End With
End If
Dim shapeEX_alert As Shape
Set shapeEX_alert = acShapes("shapeEX_alert")
With shapeEX_alert.TextFrame.Characters
.text = "ShapeEX メッセージ:" & vbCrLf & vbCrLf
.text = .text & messstr & vbCrLf
If Not waystr = "" Then
.text = .text & "→ " & waystr & vbCrLf
End If
.text = .text & vbCrLf & vbCrLf & "クリックするとメッセージを消します"
.Font.Color = messcol
End With
End Sub
''関数 39 / 52
Sub alert_click()
acShapes("shapeEX_alert").Delete
End Sub
'以前を取得
''関数 40 / 52
Private Function getSvcsBefore(targetSvc As Shape, Optional connectorHistory As String = "") As Collection
Set getSvcsBefore = New Collection
getSvcsBefore.add targetSvc
Dim connectors As Collection
Dim connector As Shape
Dim pair As Collection
'順 ※初回はスキップ
If Not connectorHistory = "" Then
Set connectors = searchConnectorsOnArm(targetSvc, True) '3
'check
If connectors.Count = 0 Then Exit Function
For Each connector In connectors
'check 未処理コネクタである
If Not connectorHistory Like "*[[]" & connector.name & "]*" Then
connectorHistory = connectorHistory & "[" & connector.name & "]"
Set pair = connectorName2arms(connector.name)
getSvcsBefore.add connector
'順
Collection_concat getSvcsBefore, getSvcsAfter(pair("child"))
End If
Next connector
End If
'逆
Set connectors = searchConnectorsOnArm(targetSvc, False) '4
'check その方向のコネクタなし?
If connectors.Count = 0 Then Exit Function
For Each connector In connectors
'check 未処理コネクタである
If Not connectorHistory Like "*[[]" & connector.name & "]*" Then
connectorHistory = connectorHistory & "[" & connector.name & "]"
Set pair = connectorName2arms(connector.name)
getSvcsBefore.add connector
Collection_concat getSvcsBefore, getSvcsBefore(pair("parent"), connectorHistory)
End If
Next connector
End Function
''関数 41 / 52
Private Sub Collection_concat(c1 As Collection, c2 As Collection)
Dim theShape As Shape
For Each theShape In c2
c1.add theShape
Next theShape
End Sub
'以降を取得
''関数 42 / 52
Public Function getSvcsAfter(targetSvc As Shape) As Collection
'仕様
'コネクタを頼りに走査するので、結果はツリー順に並ぶ。
Dim pair As Collection
Set getSvcsAfter = New Collection
'check.
If targetSvc Is Nothing Then Exit Function
getSvcsAfter.add targetSvc
If targetSvc.name Like "*>*" Then
'targetSvcがコネクタの場合
Set pair = connectorName2arms(targetSvc.name)
Collection_concat getSvcsAfter, getSvcsAfter(pair("child"))
Else
'targetShapeがコネクタではない場合
Dim connectors As Collection
Set connectors = searchConnectorsOnArm(targetSvc, True) '2
'check コネクタがない
If connectors.Count = 0 Then Exit Function
Dim connector As Shape
For Each connector In connectors
getSvcsAfter.add connector
Set pair = connectorName2arms(connector.name)
Collection_concat getSvcsAfter, getSvcsAfter(pair("child"))
Next connector
End If
End Function
'その図形が持つコネクタを得る
''関数 43 / 52
Function searchConnectorsOnArm(targetArm As Shape, order As Boolean) As Collection '8
Dim nameKey As String
If order Then
nameKey = targetArm.name & ">*"
Else
nameKey = "*>" & targetArm.name
End If
'変更 未検証
Set searchConnectorsOnArm = searchShapesByKey(nameKey)
' Dim theShape As Shape
' For Each theShape In acShapes
' If theShape.name Like keyName Then
' 'targetShapeが親になっているコネクタを見つけたら
' searchConnectorsOnArm.add theShape
' End If
' Next theShape
End Function
'コネクタをすべて選択
''関数 44 / 52
Public Sub ButtonSelectConnectors_Click(ribbonControl As IRibbonControl)
sw_connectorVisible = True
updateConnectorVisible
Range("A1").Select '選択解除
Dim theShape As Shape
For Each theShape In acShapes
If theShape.name Like "*>*" Then
theShape.Select Replace:=False
End If
Next theShape
End Sub
'コネクタの表示非表示
''関数 45 / 52
Private Sub updateConnectorVisible()
Dim theShape As Shape
For Each theShape In acShapes
If theShape.name Like "*>*" Then
theShape.Visible = sw_connectorVisible
theShape.ZOrder msoBringForward '最前面にする
End If
Next theShape
End Sub
'-o
'すべてを選択
''関数 46 / 52
Public Sub ButtonSelectAll_Click(ribbonControl As IRibbonControl)
Dim allSvcs As Collection
Set allSvcs = getAllSvcs()
Dim first As Boolean
first = True
Dim svc As Shape
For Each svc In allSvcs
If first Then
svc.Select
first = False
Else
svc.Select Replace:=False
End If
Next svc
End Sub
'-o
'すべてのアームとコネクタを取得
''関数 47 / 52
Private Function getAllSvcs() As Collection
Set getAllSvcs = searchUTL(True, True)
End Function
'-o
'すべてのアームを取得
''関数 48 / 52
Private Function getAllArms() As Collection
Set getAllArms = searchUTL(True, False)
End Function
'-o
'すべてのコネクタを取得
''関数 49 / 52
Private Function getAllConnectors() As Collection
Set getAllConnectors = searchUTL(False, True)
End Function
'-o
''関数 50 / 52
Private Function searchUTL(armsFLG As Boolean, connectorsFLG As Boolean) As Collection
'仕様
'ツリー順ではない
'check.
If Not armsFLG And Not connectorsFLG Then
MsgBox "searchUTLの引数が両方ともfalseなのは想定外"
Exit Function
End If
'コネクタをすべて取得
Dim connectors As Collection
Set connectors = searchShapesByKey("*>*")
'check.
If (Not armsFLG) And connectorsFLG Then
Set searchUTL = connectors
Exit Function
End If
'コネクタに連なる組をそれぞれ取得
Set searchUTL = New Collection
Dim connector As Shape
For Each connector In connectors
Dim pair As Collection
Set pair = connectorName2arms(connector.name)
If connectorsFLG Then searchUTL.add connector
If Not pair("parent") Is Nothing Then searchUTL.add pair("parent")
If Not pair("child") Is Nothing Then searchUTL.add pair("child")
Next connector
End Function
'-o
'検索キーに適合する名前を持つ図形を検索する
''関数 51 / 52
Private Function searchShapesByKey(nameKey As String) As Collection
Set searchShapesByKey = New Collection
Dim theShape As Shape
For Each theShape In acShapes
If theShape.name Like nameKey Then
searchShapesByKey.add theShape
End If
Next theShape
End Function
'-o
'コネクタの名前に記載された2つのarmを返す
''関数 52 / 52
Function connectorName2arms(connectorName As String) As Collection
'コネクタの名前を分割
Dim tokens() As String
tokens = Split(connectorName, ">")
Set connectorName2arms = New Collection
If tokens(0) = "" Then
connectorName2arms.add Nothing, "parent"
ElseIf Collection_existsByName(acShapes, tokens(0)) Then
connectorName2arms.add ActiveSheet.Shapes(tokens(0)), "parent"
Else
connectorName2arms.add Nothing, "parent"
End If
If tokens(1) = "" Then
connectorName2arms.add Nothing, "child"
ElseIf Collection_existsByName(acShapes, tokens(1)) Then
connectorName2arms.add ActiveSheet.Shapes(tokens(1)), "child"
Else
connectorName2arms.add Nothing, "child"
End If
End Function
JavaScriptはお休みにして、Excelで作成しているSVC(Side View Character)のツールで作成した「画像」(剣を持っている女性)をちょっと載せています。